fix(extensions): make mandatory-hook dispatch contract self-contained#2901
Conversation
mnriem
left a comment
There was a problem hiding this comment.
Thanks for the contribution. A couple of questions before we can move forward:
-
Orchestrator claim: The PR body references
specify runand "CLI orchestrators that watch agent output" as existing consumers of theEXECUTE_COMMAND:directive. Can you point to the code path where this dispatch actually happens today? If no orchestrator currently consumes the directive, the backward-compatibility framing is misleading and the PR description should be corrected. -
AI disclosure: Was this PR authored with AI assistance? If so, please disclose per contributing guidelines.
The underlying fix (agent self-dispatches mandatory hooks in agent-direct sessions) addresses a real problem, but the bar is high for sweeping template changes and we need confidence the framing accurately reflects the codebase.
|
closed this by accident while editing the description - reopened. on the orchestrator question: you're right, I rechecked and couldn't find anything in the repo that actually consumes and yes, this was built with AI assistance (Claude Code) - code, tests and description, reviewed by me. added the if the template changes are too broad I can cut it down to just the format_hook_message() change + tests and do the |
There was a problem hiding this comment.
Pull request overview
This PR strengthens the extension mandatory-hook “dispatch contract” so agent-direct invocations (Claude Code / Cursor / Codex, etc.) don’t silently emit EXECUTE_COMMAND: without actually running the hook, by explicitly instructing the agent to self-dispatch and wait.
Changes:
- Updates runtime hook messaging to explain that
EXECUTE_COMMAND:is a signal (not a dispatcher) and that mandatory hooks must be invoked and awaited. - Updates all bundled command templates to embed the same dispatch-contract guidance and tightens “Done When” criteria.
- Adds/extends tests to prevent regressions in both runtime messages and bundled templates.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/extensions.py |
Extends mandatory-hook message text to instruct self-dispatch. |
templates/commands/analyze.md |
Adds dispatch-contract bullet after mandatory hook blocks. |
templates/commands/checklist.md |
Adds dispatch-contract bullet after mandatory hook blocks. |
templates/commands/clarify.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/constitution.md |
Adds dispatch-contract bullet after mandatory hook blocks. |
templates/commands/implement.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/plan.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/specify.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/tasks.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/taskstoissues.md |
Adds dispatch-contract bullet after mandatory hook blocks. |
extensions/EXTENSION-API-REFERENCE.md |
Documents the dispatch contract for extension authors. |
tests/test_extensions.py |
Adds tests for the runtime message contract and template coverage. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 12/12 changed files
- Comments generated: 19
|
Please address Copilot feedback |
|
Yes, the template changes are way too broad. We should look into a fix with a smaller surface |
|
makes sense, agree it's too broad. while trying to shrink it I found the reason it spread: format_hook_message() in extensions.py isn't actually reached at runtime its only caller is check_hooks_for_event(), which nothing in the CLI or agent path calls. the hook block is emitted by the agent following the command-template instructions (it reads .specify/extensions.yml and outputs the block itself), so the templates are where the behavior actually lives hence all 9. two smaller options: any preference on direction? |
|
Pick one and lets see how it looks |
5339123 to
aabc7c7
Compare
|
went with option 1 - one instruction line after each mandatory-hook block, example blocks untouched. rebased on current main so the diff is clean: 9 files, 18 lines. |
|
@mnriem this is ready for another look when you have time - went with the minimal option (one line per template, examples untouched, 9 files / 18 lines). no rush, just flagging it's been sitting since the rebase. |
aabc7c7 to
5ec7882
Compare
|
Please address Copilot feedback |
… directive In agent-direct invocations nothing watches agent output for the EXECUTE_COMMAND: directive, so a mandatory hook that is only emitted never runs and the failure is silent (github#2730). Add one line after each mandatory-hook block instructing the agent to actually invoke the hook and wait for it before continuing. The instruction tells the agent to run the hook the way it would run the command itself in the current agent/session, and notes the invocation may differ from the literal {command} id shown in the block (e.g. skills-mode agents run it as /skill:speckit-... or $speckit-...), so it stays correct outside the default slash-command form. Fixes github#2730
5ec7882 to
07faeef
Compare
|
addressed both Copilot points:
also rebased on current main, diff is 10 files / 20 lines. |
|
Thank you! |
… directive (github#2901) In agent-direct invocations nothing watches agent output for the EXECUTE_COMMAND: directive, so a mandatory hook that is only emitted never runs and the failure is silent (github#2730). Add one line after each mandatory-hook block instructing the agent to actually invoke the hook and wait for it before continuing. The instruction tells the agent to run the hook the way it would run the command itself in the current agent/session, and notes the invocation may differ from the literal {command} id shown in the block (e.g. skills-mode agents run it as /skill:speckit-... or $speckit-...), so it stays correct outside the default slash-command form. Fixes github#2730
Fixes #2730
Problem
The
EXECUTE_COMMAND:directive emitted foroptional: falsehooks is meant as a dispatch signal. In agent-direct invocations (a slash command run inside Claude Code, Cursor, Codex, etc. - the default pattern for extension users), nothing watches for it. The agent emits the directive, waits, and moves on - the hook never runs, and the failure is completely silent.I could not find any code path in this repository that consumes the directive:
format_hook_message()'s only caller ischeck_hooks_for_event(), which itself has no production callers. So in practice the bundled command templates are what drive hook emission - the agent reads.specify/extensions.ymland emits the block by following the template instructions. That makes the templates the right place to fix this.Changes
templates/commands/*.md(all 10 command templates): added one instruction line after each mandatory-hook block (pre- and post-execution, 20 sites). It tells the agent that emitting the block alone does not run the hook - it must actually invoke the hook and wait for completion before continuing./{command}. It tells the agent to run the hook the way it would run the command itself in the current agent/session, and notes the invocation may differ from the literal{command}id shown in the block (e.g. skills-mode agents run it as/skill:speckit-...or$speckit-...). This keeps it correct outside the default slash-command form.Scope
This is the minimal version of an earlier, broader change, per maintainer feedback to keep the surface small. The example blocks are left untouched; only a single instruction line is added per mandatory-hook block. No runtime code, API reference, or tests are changed - the fix is entirely in the bundled template instructions the agent follows.
AI disclosure
This PR was authored with AI assistance (Claude Code); the change and this description are under my direction and review.